package com.example.loggingconsumer;

import com.example.loggingconsumer.event.PersonSaved;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

@RestController
@Slf4j
@SpringBootApplication
public class LoggingConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(LoggingConsumerApplication.class, args);
    }

    private PersonSavedProjection<PersonSaved> personSavedProjection;

    @Autowired
    public void setPersonSavedProjection(PersonSavedProjection<PersonSaved> personSavedProjection) {
        this.personSavedProjection = personSavedProjection;
    }

    @PostMapping("/persons")
    public ResponseEntity<String> savePerson(@RequestBody String name) {
        log.info("Save the person and send a person saved event.");
        personSavedProjection.project(new PersonSaved(name));
        return ResponseEntity.ok("OK");
    }

    @Bean
    public Function<PersonSaved, PersonSaved> flavorPersonSaved() {
        return personSaved -> {
            log.info(String.format("Flavor: %s", personSaved.getName()));
            personSaved.setName(String.format("%s:%s", personSaved.getName(), "Flavored"));
            return personSaved;
        };
    }

    @Bean
    public Function<PersonSaved, PersonSaved> moreDisposeAfterFlavorPersonSaved() {
        return personSaved -> {
            log.info(String.format("More dispose: %s", personSaved.getName()));
            personSaved.setName(String.format("%s:%s", personSaved.getName(), " More dispose"));
            return personSaved;
        };
    }

    @Bean
    public Consumer<PersonSaved> printPersonSaved() {
        return personSaved -> log.info(String.format("Print: %s", personSaved.getName()));
    }

    @Component
    static class PersonSavedProjection<T extends PersonSaved> {
        private BlockingQueue<T> cacheBlockQueue = new LinkedBlockingQueue<>();

        void project(T t) {
            cacheBlockQueue.offer(t);
        }

        @Bean
        public Supplier<T> emitPersonSaved() {
            return () -> {
                T personSaved = null;
                try {
                    personSaved = cacheBlockQueue.take();
                } catch (InterruptedException e) {
                    log.info("Query poll being interrupted.");
                }
                // Probably save the event first for reconciliation?
                log.info("Trigger a person saved event. {}", personSaved);
                return personSaved;
            };
        }
    }
}
